/**
 * Copyright (c) 2018-2028.
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sailmi.common.tool.utils;

import com.sailmi.common.constant.RoleConstant;
import com.sailmi.common.constant.SecureConstant;
import com.sailmi.common.constant.TokenConstant;
import com.sailmi.common.exception.SecureException;
import com.sailmi.common.model.auth.AuthInfo;
import com.sailmi.common.model.auth.AuthUser;
import com.sailmi.common.secure.client.IClientDetails;
import com.sailmi.common.tool.jackson.JsonUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * Secure工具类
 *
 *
 */

public class SecureUtil {
	private static final String AUTH_USER_REQUEST_ATTR = "_AUTH_USER_REQUEST_ATTR_";
	private static final Logger log = LoggerFactory.getLogger(SecureUtil.class);
	private final static String HEADER = TokenConstant.HEADER;
	private final static String BEARER = TokenConstant.BEARER;
	private final static String ACCOUNT = TokenConstant.ACCOUNT;
	private final static String USER_ID = TokenConstant.USER_ID;
	private final static String ROLE_ID = TokenConstant.ROLE_ID;
	private final static String USER_NAME = TokenConstant.USER_NAME;
	private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
	private final static String TENANT_ID = TokenConstant.TENANT_ID;
	private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
	private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
	private final static String ENTERPRISE_ID = TokenConstant.ENTERPRISE_ID;

	public static String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
	/**
	 * 获取用户信息
	 *
	 * @return AuthUser
	 */
	public static AuthInfo getAuthInfo() {
		HttpServletRequest request = WebUtil.getRequest();
		if (request == null) {
			return null;
		}
		// 优先从 request 中获取
		String authInfoStr = request.getHeader(AUTH_USER_REQUEST_ATTR);
		if(Objects.nonNull(authInfoStr)){
			authInfoStr = UrlUtil.decodeURL(authInfoStr, Charsets.UTF_8);
			AuthInfo authInfo = JsonUtil.parse(authInfoStr, AuthInfo.class);
			if (authInfo == null) {
				authInfo = getAuthInfo(request);
				if (authInfo != null) {
					// 设置到 request 中
					request.setAttribute(AUTH_USER_REQUEST_ATTR, authInfo);
				}
			}
			return authInfo;
		}
		return null;
	}



	/**
	 * 获取用户信息
	 *
	 * @param request request
	 * @return AuthUser
	 */
	public static AuthInfo getAuthInfo(HttpServletRequest request) {
		Claims claims = getClaims(request);
		if (claims == null) {
			return null;
		}
		String clientId = Func.toStr(claims.get(SecureUtil.CLIENT_ID));
		Long userId = Func.toLong(claims.get(SecureUtil.USER_ID));
		String tenantId = Func.toStr(claims.get(SecureUtil.TENANT_ID));
		String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
		String account = Func.toStr(claims.get(SecureUtil.ACCOUNT));
		String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME));
		String userName = Func.toStr(claims.get(SecureUtil.USER_NAME));
		String enterpriseId = Func.toStr(claims.get(SecureUtil.ENTERPRISE_ID));
		AuthInfo authInfo = new AuthInfo();
		authInfo.setClientId(clientId);
		authInfo.setUserId(String.valueOf(userId));
		authInfo.setTenantId(tenantId);
		authInfo.setAccount(account);
		authInfo.setAuthority(roleId);
		authInfo.setUserName(userName);
		authInfo.setEnterprise(enterpriseId);
		return authInfo;
	}

	/**
	 * 是否为超管
	 *
	 * @return boolean
	 */
	public static boolean isAdministrator() {
		return StringUtil.containsAny(getUserRole(), RoleConstant.ADMIN);
	}

	/**
	 * 获取用户id
	 *
	 * @return userId
	 */
	public static Long getUserId() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? -1 : Long.valueOf(user.getUserId());
	}

	/**
	 * 获取用户id
	 *
	 * @param request request
	 * @return userId
	 */
	public static Long getUserId(HttpServletRequest request) {
		AuthInfo user = getAuthInfo(request);
		return (null == user) ? -1 : Long.valueOf(user.getUserId());
	}

	/**
	 * 获取用户账号
	 *
	 * @return userAccount
	 */
	public static String getUserAccount() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getAccount();
	}

	/**
	 * 获取用户账号
	 *
	 * @param request request
	 * @return userAccount
	 */
	public static String getUserAccount(HttpServletRequest request) {
		AuthInfo user = getAuthInfo(request);
		return (null == user) ? StringPool.EMPTY : user.getAccount();
	}

	/**
	 * 获取用户名
	 *
	 * @return userName
	 */
	public static String getUserName() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getUserName();
	}

	/**
	 * 获取用户名
	 *
	 * @param request request
	 * @return userName
	 */
	public static String getUserName(HttpServletRequest request) {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getUserName();
	}
	/**
	 * 获取用角色
	 *
	 * @return userName
	 */
	public static String getUserRole() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getAuthority();
	}

	/**
	 * 获取用角色
	 *
	 * @param request request
	 * @return userName
	 */
	public static String getUserRole(HttpServletRequest request) {
		AuthInfo user = getAuthInfo(request);
		return (null == user) ? StringPool.EMPTY : user.getAuthority();
	}

	/**
	 * 获取租户ID
	 *
	 * @return tenantId
	 */
	public static String getTenantId() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getTenantId();
	}

	/**
	 * 获取租户ID
	 *
	 * @param request request
	 * @return tenantId
	 */
	public static String getTenantId(HttpServletRequest request) {
		AuthInfo user = getAuthInfo(request);
		return (null == user) ? StringPool.EMPTY : user.getTenantId();
	}

	/**
	 * 获取客户端id
	 *
	 * @return tenantId
	 */
	public static String getClientId() {
		AuthInfo user = getAuthInfo();
		return (null == user) ? StringPool.EMPTY : user.getClientId();
	}

	/**
	 * 获取客户端id
	 *
	 * @param request request
	 * @return tenantId
	 */
	public static String getClientId(HttpServletRequest request) {
		AuthInfo user = getAuthInfo(request);
		return (null == user) ? StringPool.EMPTY : user.getClientId();
	}

	/**
	 * 获取Claims
	 *
	 * @param request request
	 * @return Claims
	 */
	public static Claims getClaims(HttpServletRequest request) {
		String auth = request.getHeader(SecureUtil.HEADER);
		if (StringUtil.isNotBlank(auth) && auth.length() > AUTH_LENGTH) {
			String headStr = auth.substring(0, 6).toLowerCase();
			if (headStr.compareTo(SecureUtil.BEARER) == 0) {
				auth = auth.substring(7);
				return SecureUtil.parseJWT(auth);
			}
		} else {
			String parameter = request.getParameter(SecureUtil.HEADER);
			if (StringUtil.isNotBlank(parameter)) {
				return SecureUtil.parseJWT(parameter);
			}
		}
		return null;
	}

	/**
	 * 获取请求头
	 *
	 * @return header
	 */
	public static String getHeader() {
		return getHeader(Objects.requireNonNull(WebUtil.getRequest()));
	}

	/**
	 * 获取请求头
	 *
	 * @param request request
	 * @return header
	 */
	public static String getHeader(HttpServletRequest request) {
		return request.getHeader(HEADER);
	}

	/**
	 * 解析jsonWebToken
	 *
	 * @param jsonWebToken jsonWebToken
	 * @return Claims
	 */
	public static Claims parseJWT(String jsonWebToken) {
		try {
			return Jwts.parser()
				.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY))
				.build()
				.parseClaimsJws(jsonWebToken).getBody();
		} catch (Exception ex) {
			return null;
		}
	}


	/**
	 * 获取过期时间(次日凌晨3点)
	 *
	 * @return expire
	 */
	public static long getExpire() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_YEAR, 1);
		cal.set(Calendar.HOUR_OF_DAY, 3);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal.getTimeInMillis() - System.currentTimeMillis();
	}

	/**
	 * 客户端信息解码
	 */
	public static String[] extractAndDecodeHeader() {
		// 获取请求头客户端信息
		String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
		log.info("SecureUtil:extractAndDecodeHeader:header:"+header);
		header = Func.toStr(header).replace(SecureConstant.BASIC_HEADER_PREFIX_EXT, SecureConstant.BASIC_HEADER_PREFIX);
		log.info("SecureUtil:extractAndDecodeHeader:header after replace:"+header);
		if (!header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
			throw new SecureException("No client information in request header");
		}
		byte[] decoded;
		try {
			byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
			decoded = Base64.getDecoder().decode(base64Token);
		} catch (IllegalArgumentException var7) {
			throw new RuntimeException("Failed to decode basic authentication token");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}

		String token = null;
		try {
			token = new String(decoded, Charsets.UTF_8_NAME);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
		log.info("SecureUtil:extractAndDecodeHeader:token:"+token);
		int index = token.indexOf(StringPool.COLON);
		if (index == -1) {
			throw new RuntimeException("Invalid basic authentication token");
		} else {
			return new String[]{token.substring(0, index), token.substring(index + 1)};
		}
	}

	/**
	 * 获取请求头中的客户端id
	 */
	public static String getClientIdFromHeader() {
		String[] tokens = extractAndDecodeHeader();
		assert tokens.length == 2;
		return tokens[0];
	}

	/**
	 * 校验Client
	 *
	 * @param clientId     客户端id
	 * @param clientSecret 客户端密钥
	 * @return boolean
	 */
	private static boolean validateClient(IClientDetails clientDetails, String clientId, String clientSecret) {
		if (clientDetails != null) {
			return StringUtil.equals(clientId, clientDetails.getClientId()) && StringUtil.equals(clientSecret, clientDetails.getClientSecret());
		}
		return false;
	}

}
